package org.example;

import ch.hsr.geohash.GeoHash;
import ch.hsr.geohash.WGS84Point;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.io.GeohashUtils;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKBReader;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Hello world!
 *
 */

public class App
{
    private static SpatialContext spatialContext = SpatialContext.GEO;

    public static void main( String[] args ) throws ClassNotFoundException,  SQLException {
        Class<?> driver=Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/test";
        Properties p = new Properties();
        p.setProperty("user","root");
        p.setProperty("password","lly5201314");
        Connection connection = DriverManager.getConnection(url,p);
        int num = 1;
        ExecutorService executorService = Executors.newFixedThreadPool(num);
        for (int i = 0; i < num; i++) {
            Random r = new Random();
            double lon = r.nextDouble() * 0.01D +  118.89;
            double lat = r.nextDouble() * 0.01D + 32.10D;
//            lon =  118.892144D;
//            lat = 32.105055D;
            //计算geohash
            String GeoHashStr = GeohashUtils.encodeLatLon(lat, lon, 10);
            String[] subGeoHash = GeohashUtils.getSubGeohashes(GeoHashStr);

//            System.out.println("geoHash "+GeoHashStr);
//            for (String geoHash : subGeoHash) {
//                System.out.println("subGeoHash "+geoHash);
//            }
            int pre = 8;
            //点转hash
            GeoHash geoHash = GeoHash.withBitPrecision(lat, lon,pre*5);
//            System.out.println(geoHash.toBase32());
//            System.out.println();

            executorService.submit(()->{
                try {
                    //附近8个点
                    GeoHash[] adjacent = geoHash.getAdjacent();
                    String geoHashStr = geoHash.toBase32();
                    String sql = "select * from z_gis where 1=1 ";

                    StringBuffer sb = new StringBuffer(sql);
                    sb.append("and geohash like concat('"+geoHashStr+"','%')");
                    //解决边界问题 所以要家上附近8个点
                    for (int j = 0; j < adjacent.length; j++) {
                        GeoHash hash = adjacent[j];
                        String adjacentHash = hash.toBase32();
                        String sql1  = "or concat('"+adjacentHash+"','%') ";
                        sb.append(sql1);
                    }
                    System.out.println("sql " + sb.toString());
                    PreparedStatement statement = connection.prepareStatement(sb.toString());
//                    for (int j = 0; j < adjacent.length; j++) {
//                        GeoHash hash = adjacent[j];
//                        String adjacentHash = hash.toBase32();
//                        statement.setString(j+1,adjacentHash);
//                    }
                    long s = System.currentTimeMillis();
                    ResultSet resultSet = statement.executeQuery();
                    long e = System.currentTimeMillis();
                    int rowCount = resultSet.getRow();
                    System.out.println("geoHashStr:"+geoHashStr+" time "+(e-s)+"ms size " +rowCount);
                    //统计支线距离
                    while (resultSet.next()){
                        InputStream is = resultSet.getBinaryStream(3);
                        //mysql 类型转java对象
                        Geometry geometry = getGeometryFromInputStream(is);
//                        System.out.println("id "+resultSet.getString(1)+",name "+resultSet.getString(2)+",gis "+geometry.toString()+",geohash "+resultSet.getString(4)+"");
                        WGS84Point point = geoHash.getPoint();
                        double lon1 = point.getLongitude();
                        double lat1 = point.getLatitude();
                        double lon2 = geometry.getInteriorPoint().getX();
                        double lat2 = geometry.getInteriorPoint().getY();
                        double n = near(lat1, lon1, lat2, lon2);
//                        System.out.println("near km" +a);
                        //计算亮点距离，采用半正弦弧度算法
                        double h = DistanceUtils.distHaversineRAD(lat1, lon1, lat2, lon2);
                        double v = DistanceUtils.distVincentyRAD(lat1, lon1, lat2, lon2);
                        double l = DistanceUtils.distLawOfCosinesRAD(lat1, lon1, lat2, lon2);

                        System.out.println("n "+ km2m(n) +"m");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                return "";
            });
        }
//        while(!executorService.isShutdown()){
//            executorService.shutdown();
//        }
    }

    /**
     * 计算两点距离 单位km
     * @param lat1
     * @param lon1
     * @param lat2
     * @param lon2
     * @return
     */
    static double near(double lat1 ,double lon1,double lat2,double lon2){
        return spatialContext.calcDistance(spatialContext.makePoint(lon1, lat1),
                spatialContext.makePoint(lon2, lat2)) * DistanceUtils.DEG_TO_KM;
    }

    /**
     * mysql Geometry 转 java Geometry
     * @param inputStream
     * @return
     * @throws Exception
     */
    private static Geometry getGeometryFromInputStream(InputStream inputStream) throws Exception {

        Geometry dbGeometry = null;

        if (inputStream != null) {

            // 把二进制流转成字节数组
            //convert the stream to a byte[] array
            //so it can be passed to the WKBReader
            byte[] buffer = new byte[255];

            int bytesRead = 0;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesRead);
            }

            // 得到字节数组
            byte[] geometryAsBytes = baos.toByteArray();
            // 字节数组小于5，说明geometry有问题
            if (geometryAsBytes.length < 5) {
                throw new Exception("Invalid geometry inputStream - less than five bytes");
            }

            //first four bytes of the geometry are the SRID,
            //followed by the actual WKB.  Determine the SRID
            //这里是取字节数组的前4个来解析srid
            byte[] sridBytes = new byte[4];
            System.arraycopy(geometryAsBytes, 0, sridBytes, 0, 4);
            boolean bigEndian = (geometryAsBytes[4] == 0x00);
            // 解析srid
            int srid = 0;
            if (bigEndian) {
                for (int i = 0; i < sridBytes.length; i++) {
                    srid = (srid << 8) + (sridBytes[i] & 0xff);
                }
            } else {
                for (int i = 0; i < sridBytes.length; i++) {
                    srid += (sridBytes[i] & 0xff) << (8 * i);
                }
            }

            //use the JTS WKBReader for WKB parsing
            WKBReader wkbReader = new WKBReader();
            // 使用geotool的WKBReader 把字节数组转成geometry对象。
            //copy the byte array, removing the first four
            //SRID bytes
            byte[] wkb = new byte[geometryAsBytes.length - 4];
            System.arraycopy(geometryAsBytes, 4, wkb, 0, wkb.length);
            dbGeometry = wkbReader.read(wkb);
            dbGeometry.setSRID(srid);
        }

        return dbGeometry;

    }

    /**
     * 千米转米
     * @param km
     * @return
     */
    private static double km2m(double km){
        return km * 1000D;
    }
}
